package jp.joogoo.web.controller;

import java.util.List;
import java.util.logging.Logger;

import jp.joogoo.web.meta.M_serviceMeta;
import jp.joogoo.web.model.M_joogoo;
import jp.joogoo.web.model.M_service;
import jp.joogoo.web.service.AuthorizeService;

import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthNotAuthorizedException;

import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
import org.slim3.datastore.Datastore;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.appengine.api.users.UserService;
import com.google.appengine.repackaged.org.json.JSONObject;

/**
 * OAuth で認証するためのコントローラーです。
 * POST リクエストが来た場合はサービスプロバイダーをルックアップして認証を始めます。
 * 一時トークンを取得できた場合は認可のページにリダイレクトします。
 * GET リクエストで来た場合はサービスプロバイダーから戻ってきたと想定します。
 * PIN コードを確認して、認証トークンを取得します。
 * 
 * 認証トークンを取得したら、初期データを取得するために、非同期にリクエストを投げます。
 * 非同期処理はタスクキューで実現します。
 * 
 * @since 1.0
 * @author skitazaki
 */
public class SignupController extends Controller {

    private static final M_serviceMeta serviceMeta = new M_serviceMeta();

    private static final AuthorizeService auth = new AuthorizeService();

    private static final Logger logger = Logger.getLogger(SignupController.class.getName());

    @Override
	protected Navigation run() throws Exception {
        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();
        if (user == null) {
            return redirect("/");
        }

        if (isPost()) {
            String s = asString("service");
            if (s == null || s.length() == 0) {
                // invalid request, parameter missing.
                return redirect("/");
            }
            Key key = KeyFactory.stringToKey(s);
            M_service service = Datastore.get(serviceMeta, key);
            if (service == null) {
                // service not found.
                return redirect("/");
            }

            // セッションに保存するより memcached の方が良いかも。
            // -> M_joogoo に認証状態を持たせてあげるようにする。セッションはいらない。
            // -> 実装時間が足りなそうなので、やっぱりセッションを使う。 (skitazaki)
            sessionScope("service", service);

            String callbackUrl = "http://" + request.getServerName();
            if (request.getServerPort() > 0) {
                callbackUrl += ":" + request.getServerPort();
            }
            if (request.getServletPath() != null) {
                callbackUrl += request.getServletPath();
            }
            OAuthConsumer consumer = service.getConsumer();
            OAuthProvider provider = service.getProvider();
            try {
                String authUrl = provider.retrieveRequestToken(consumer, callbackUrl);
                sessionScope("tokenKey", consumer.getToken());
                sessionScope("tokenSecret", consumer.getTokenSecret());
                return redirect(authUrl + "&oauth_callback=" + callbackUrl);
            } catch (OAuthCommunicationException e) {
                logger.severe(provider.getRequestTokenEndpointUrl() + ", " + e.getMessage());
            } catch (OAuthNotAuthorizedException e) {
                logger.info(provider.getRequestTokenEndpointUrl() + ", " + callbackUrl + ", " + e.getMessage());
            }
        } else if (isGet()) {
            Object service_ = sessionScope("service");
            if (service_ == null || !(service_ instanceof M_service)) {
                logger.info("invalid session.");
                return redirect("/");
            }
            
            M_service service = (M_service)service_;

            // callback handler to verify PIN code
            String verifier = asString("oauth_verifier");
            if (verifier == null || verifier.length() == 0) {
                logger.info("no verifier code is found. service=" + service.getServiceName());
                return redirect("/");
            }
            Object key = sessionScope("tokenKey");
            Object secret = sessionScope("tokenSecret");
            if (key == null || secret == null) {
                logger.info("invalid state, temporal token is missing. service=" + service.getServiceName());
                return redirect("/");
            }

            OAuthConsumer consumer = service.getConsumer();
            consumer.setTokenWithSecret(key.toString(), secret.toString());
            OAuthProvider provider = service.getProvider();
            try {
                provider.setOAuth10a(true); // force to use verifier code whatever returned on first request.
                provider.retrieveAccessToken(consumer, verifier);
                JSONObject tokenObject = new JSONObject();
                tokenObject.put("key", consumer.getToken());
                tokenObject.put("secret", consumer.getTokenSecret());
                // TODO: 数値ではなくハッシュ値を計算した方が良い。
                String userId = user.getUserId();
                // TODO: クエリにおける参照の使い方がよく分からないのでオンメモリでループを回してます。サービスを拡張する？
                List<M_joogoo> accounts = auth.getAccounts(userId);
                M_joogoo account = null;
                if (accounts.size() > 0) {
                    for (M_joogoo a : accounts) {
                        if (a.getMServiceRef().getModel().equals(service)) {
                            account = a;
                        }
                    }
                }
                if (account == null) {
                    account = new M_joogoo();
                    account.setUserId(userId);
                    account.getMServiceRef().setModel(service);
                }
                account.setToken(tokenObject.toString());
                Datastore.put(account);
                // 最初のデータ取得を非同期に実行させます。
                Queue queue = QueueFactory.getDefaultQueue();
                queue.add(TaskOptions.Builder.withUrl("/fetcher").param("userId", userId));
                return redirect("/dashboard");
            } catch (OAuthCommunicationException e) {
                logger.severe(provider.getAccessTokenEndpointUrl() + ", " + e.getMessage());
            } catch (OAuthNotAuthorizedException e) {
                logger.info(provider.getAccessTokenEndpointUrl() + ", " + e.getMessage());
            }
        } else {
            // illegal HTTP request method.
            return redirect("/");
        }

        return redirect("/");
	}
}
